package com.google.code.guava.test;

import static com.google.common.base.Predicates.not;

import java.util.Collection;
import java.util.Iterator;
import java.util.Set;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;

import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.ForwardingSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

public class GuavaMatchers{

    static class EqualsConsideringSet<E> extends ForwardingSet<E>{

        public EqualsConsideringSet(){}

        public EqualsConsideringSet(final Collection<? extends E> existing){
            addAll(existing);
        }

        @Override
        public boolean add(final E candidate){
            for(final E element : inner){
                if(Objects.equal(element, candidate)) return false;
            }
            return inner.add(candidate);
        }

        @Override
        public boolean addAll(final Collection<? extends E> collection){
            boolean result = false;
            for(final E candidate : collection){
                if(add(candidate)){
                    result=true;
                }
            }
            return result;
        }

        @Override
        public boolean remove(final Object object){
            final Iterator<E> iterator = inner.iterator();
            while(iterator.hasNext()){
                final E e = iterator.next();
                if(Objects.equal(e, object)){
                    iterator.remove();
                    return true;
                }
            }
            return false;
        }

        @Override
        public boolean removeAll(final Collection<?> collection){
            boolean result = false;
            for(final Object candidate : collection){
                if(remove(candidate)){
                    result = true;
                }
            }
            return result;
        }

        private final Set<E> inner = Sets.newHashSet();

        @Override
        protected Set<E> delegate(){
            return inner;
        }

    }

    static class MutuallyEqualMatcher<T extends Collection<?>> extends
    BaseMatcher<T>{

        public boolean matches(final Object item){
            if(item instanceof Collection<?>){
                final EqualsConsideringSet<Object> set =
                    new EqualsConsideringSet<Object>((Collection<?>) item);
                return set.size() == 1;
            }
            return false;
        }

        public void describeTo(final Description description){
            description.appendText("a Collection<?> with mutually equal() values");
        }

    }

    static final class EqualContentsMatcher<T> extends BaseMatcher<Iterable<T>>{

        private final Iterable<T> other;

        private EqualContentsMatcher(final Iterable<T> other){
            this.other = other;
        }

        public void describeTo(final Description description){
            description.appendText("has equal elements with(")
            .appendValue(this.other)
            .appendText(")");
        }

        @SuppressWarnings("unchecked")
        public boolean matches(final Object item){
            final Iterable<T> iterable = (Iterable<T>) item;
            return Iterables.elementsEqual(iterable, this.other);
        }
    }

    static final class PredicateMatcher<T> extends BaseMatcher<T>{

        private final Predicate<T> predicate;

        private PredicateMatcher(final Predicate<T> predicate){
            this.predicate = predicate;
        }

        public void describeTo(final Description description){
            description.appendText("matches predicate(")
            .appendValue(this.predicate)
            .appendText(")");
        }

        @SuppressWarnings("unchecked")
        public boolean matches(final Object item){
            return this.predicate.apply((T) item);
        }
    }

    public static <T> Matcher<Iterable<T>> hasEqualContentsWith(
        final Iterable<T> other){
        return new EqualContentsMatcher<T>(other);
    }

    public static <T> Matcher<T> isNotMatchedBy(final Predicate<T> predicate){
        return new PredicateMatcher<T>(not(predicate));
    }

    public static <T> Matcher<T> isMatchedBy(final Predicate<T> predicate){
        return new PredicateMatcher<T>(predicate);
    }

    public static <T extends Collection<?>> MutuallyEqualMatcher<T> areMutuallyEqual(){
        return new MutuallyEqualMatcher<T>();
    }

}
